#ifndef __PAGECACHE_HPP__
#define __PAGECACHE_HPP__
// 第三层模型 - 页缓存，直接向操作系统申请内存，解决外碎片问题

#include "Common.hpp"
#include "PageMap.hpp"

namespace QiHai
{
	class PageCache
	{
	private:
		static PageCache _obj;
		std::mutex _mtx;  // 一整个pagecache的大锁，因为移动变化涉及到每个的变化，只能加一把大锁
		SpanList _spans[NPAGES];
		//std::unordered_map<size_t, Span*> _numOfSpan;  // 这里先用哈希表进行替代，之后效率问题会使用基数树进行一个替换
		TCMalloc_PageMap1<32 - SHIFT_PAGE> _numOfSpan;  // 使用基数树优化性能
		DefinitePoll<Span> _spanPoll;

		PageCache(){}
		PageCache(PageCache&) = delete;
		PageCache& operator=(PageCache&) = delete;
	public:
		static PageCache* PageCacheObj() { return &_obj; }
		void lock() { _mtx.lock(); }
		void unlock() { _mtx.unlock(); }

		Span* NewSpan(size_t npage);
		Span* MapObjectToSpan(void* ptr);

		// 回收Span
		void ReleaseSpanToPageCache(Span* span);
	};

	PageCache PageCache::_obj;

	///////////PageCache//////////////
	
	// 根据传入的页的个数返回对应的page
	Span* PageCache::NewSpan(size_t npage)
	{
		Span* res = nullptr;
		if (npage > NPAGES)
		{
			res = _spanPoll.New();
			// 大于128page？
			res->_pageNum = (size_t)SystemAlloc(npage) >> SHIFT_PAGE;
			res->_isUse = true;
			res->_n = npage;
			// 它是不会参与到合并的流程中去的
			//_numOfSpan[res->_pageNum] = res;
			_numOfSpan.set(res->_pageNum, res);
			return res;
		}
		else {
			// 首先根据npage直接定址法，找到双链表，查看其span是否还有
			if (!_spans[npage - 1].Empty())
			{
				res = _spans[npage - 1].PopHead();
			}
			else
			{
				// 到这里res没有现成的了，只能靠别人给他切分了
				res = _spanPoll.New();
				Span* tmp = nullptr;
				// 当前没有，那么就向下找，是否更高页号存在span，存在的话进行一个切分哦
				for (int i = npage; i < NPAGES; ++i)
				{
					if (!_spans[i].Empty())
					{
						tmp = _spans[i].PopHead();
						break;
					}
				}
				if (tmp == nullptr)
				{
					// 如果没有，那么就去系统要一块128page的大块span
					tmp = _spanPoll.New();
					tmp->_pageNum = (size_t)SystemAlloc(NPAGES) >> SHIFT_PAGE;  // 算出页号  本身就是按照页进行申请的，所以页号就能区分开我们每一个申请的页！
					tmp->_n = NPAGES;
				}

				// 此时不管你是向系统拿的还是现成的，都在这里进行切分
				res->_pageNum = tmp->_pageNum;
				res->_n = npage;
				tmp->_pageNum += npage;
				tmp->_n -= npage;

				// 此时分出新的tmp插入到对应的哈希桶中，没有分出去
				if (tmp->_n >= 1)
					_spans[tmp->_n - 1].PushBack(tmp);
				else assert(false);

				// 对于回收内存的适合，我们需要根据一个页号找到相邻的页号。那么必然是使用过的页，
				// 使用过的页可能之前就还回来了，所以分出去的页需要一个一个关联映射,并且分出去的每一份相同span的内存碎片需要找到同一份span
				// 对于没有分出去的，只需要头和尾进行映射即可

				//_numOfSpan[tmp->_pageNum] = tmp;
				//_numOfSpan[tmp->_pageNum + tmp->_n - 1] = tmp;
				_numOfSpan.set(tmp->_pageNum, tmp);
				_numOfSpan.set(tmp->_pageNum + tmp->_n - 1, tmp);
			}
		}

		res->_isUse = true;
		// 别忘记映射了哦
		for (size_t i = 0; i < (res->_n); ++i)
		{
			//_numOfSpan[res->_pageNum + i] = res;
			_numOfSpan.set(res->_pageNum + i, res);
		}
		return res;
	}

	// 根据传入的指针地址找到对应映射的Span，如果不存在说明给的地址有问题（要么程序逻辑实现错误，要么外面乱传）
	Span* PageCache::MapObjectToSpan(void* ptr)
	{
		// 根据地址算出页号
		size_t npage = (size_t)ptr >> SHIFT_PAGE;
		// 注意，因为这里是查找span，在上层，存在插入和删除，所以此函数存在一定的线程安全问题。
		//std::unique_lock<std::mutex> mtx(_mtx);  // 自动上锁和取消锁的哦  有了基数树的优化后不需要上锁了，不会存在变化了

		//auto it = _numOfSpan.find(npage);
		//if (it != _numOfSpan.end())
		//{
		//	return it->second;
		//}
		Span* tmp = (Span*)_numOfSpan.get(npage);
		if (tmp != nullptr) return tmp;
		else return nullptr;  // 存在问题
	}

	// 向上回收内存跨度(span)，并且向上和向下进行一个内存合并，解决外碎片问题。
	void PageCache::ReleaseSpanToPageCache(Span* span)
	{
		// 首先判断释放的大小是否大于128page
		if (span->_n > NPAGES)
		{
			void* ptr = (void*)(span->_pageNum << SHIFT_PAGE);
			SystemFree(ptr);
			_spanPoll.Delete(span);
		}
		else
		{
			// 先往下进行合并
			//auto findDown = _numOfSpan.find(span->_pageNum - 1);
			Span* findDown = (Span*)_numOfSpan.get(span->_pageNum - 1);
			while (findDown != nullptr)
			{
				if (findDown->_isUse || (findDown->_n + span->_n > NPAGES)) break;  // 如果需要合并的当前正在被使用或者合并的页数大于我们管理的最大页数就直接放弃合并
				// 此时可以合并了
				span->_pageNum = findDown->_pageNum;
				span->_n += findDown->_n;

				// 删除nspan的记录，并且释放掉申请的Span结构空间
				_spans[findDown->_n - 1].Erase(findDown);
				_spanPoll.Delete(findDown);
				findDown = (Span*)_numOfSpan.get(span->_pageNum - 1);
			}
			// 向上进行合并
			Span* findUp = (Span*)_numOfSpan.get(span->_pageNum + span->_n);
			while (findUp != nullptr)
			{
				if (findUp->_isUse || (findUp->_n + span->_n > NPAGES)) break;
				// 此时可以合并了
				span->_n += findUp->_n;

				// 删除nspan的记录，并且释放掉申请的Span结构空间
				_spans[findUp->_n - 1].Erase(findUp);
				_spanPoll.Delete(findUp);
				findUp = (Span*)_numOfSpan.get(span->_pageNum + span->_n);
			}

			// 合并完成后，此时span就是经过优化解决外碎片的span了
			_spans[span->_n - 1].PushBack(span);
			span->_isUse = false;
			// 注意，此时插入了新的，我们需要进行一个映射
			//_numOfSpan[span->_pageNum] = span;
			//_numOfSpan[span->_pageNum + span->_n - 1] = span;
			_numOfSpan.set(span->_pageNum, span);
			_numOfSpan.set(span->_pageNum + span->_n - 1, span);
		}
	}

}

#endif // !__PAGECACHE_HPP__
